package edu.northwestern.cbits.purple_robot_manager.probes.services; import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.facebook.model.GraphObject; import com.facebook.Response; import com.facebook.model.GraphObjectList; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.widget.Toast; import edu.emory.mathcs.backport.java.util.Collections; import edu.northwestern.cbits.purple_robot_manager.EncryptionManager; import edu.northwestern.cbits.purple_robot_manager.R; import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference; import edu.northwestern.cbits.purple_robot_manager.calibration.FacebookCalibrationHelper; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager; import edu.northwestern.cbits.purple_robot_manager.probes.Probe; import edu.northwestern.cbits.xsi.facebook.FacebookApi; import edu.northwestern.cbits.xsi.facebook.FacebookLoginActivity; public class FacebookProbe extends Probe { private static final boolean DEFAULT_ENABLED = false; private static final boolean DEFAULT_ENCRYPT = true; protected static final String HOUR_COUNT = "HOUR_COUNT"; private static final String ENABLED = "config_probe_facebook_enabled"; private static final String FREQUENCY = "config_probe_facebook_frequency"; private static final String ENCRYPT_DATA = "config_probe_facebook_encrypt_data"; private static final String DAY_COUNT = "DAY_COUNT"; private long _lastCheck = 0; @Override public String getPreferenceKey() { return "services_facebook"; } @Override public String name(Context context) { return "edu.northwestern.cbits.purple_robot_manager.probes.services.FacebookProbe"; } @Override public String title(Context context) { return context.getString(R.string.title_facebook_probe); } @Override public String probeCategory(Context context) { return context.getResources().getString(R.string.probe_external_services_category); } @Override public void enable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FacebookProbe.ENABLED, true); e.commit(); } @Override public void disable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FacebookProbe.ENABLED, false); e.commit(); } @Override public boolean isEnabled(final Context context) { final SharedPreferences prefs = Probe.getPreferences(context); if (super.isEnabled(context)) { final long now = System.currentTimeMillis(); if (prefs.getBoolean(FacebookProbe.ENABLED, FacebookProbe.DEFAULT_ENABLED)) { synchronized (this) { long freq = Long.parseLong(prefs.getString(FacebookProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY)); final boolean doHash = prefs.getBoolean(FacebookProbe.ENCRYPT_DATA, FacebookProbe.DEFAULT_ENCRYPT); if (now - this._lastCheck > freq) { this._lastCheck = now; FacebookCalibrationHelper.check(context); final String token = prefs.getString(FacebookApi.TOKEN, ""); if (token != null && token.trim().length() > 0) { final FacebookProbe me = this; final Bundle bundle = new Bundle(); bundle.putString("PROBE", me.name(context)); bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000); final ArrayList<GraphObject> items = new ArrayList<>(); final EncryptionManager em = EncryptionManager.getInstance(); FacebookApi.fetchRequest(context, token, "/me/statuses", new FacebookApi.OnRequestCallback() { @Override public void onRequestCompleted(Response response) { try { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); GraphObject obj = response.getGraphObject(); GraphObjectList<GraphObject> rawPosts = obj.getPropertyAsList("data", GraphObject.class); for (GraphObject object : rawPosts) { items.add(object); } FacebookApi.fetchRequest(context, token, "/me/posts", new FacebookApi.OnRequestCallback() { @Override public void onRequestCompleted(Response response) { try { GraphObject obj = response.getGraphObject(); GraphObjectList<GraphObject> rawPosts = obj.getPropertyAsList("data", GraphObject.class); for (GraphObject object : rawPosts) { items.add(object); } Collections.sort(items, new Comparator<GraphObject>() { @Override public int compare(GraphObject one, GraphObject two) { String oneTime = one.getProperty("updated_time").toString(); String twoTime = two.getProperty("updated_time").toString(); return oneTime.compareTo(twoTime); } }); final long mostRecent = prefs.getLong("config_probe_facebook_recent", 0); long newRecent = mostRecent; int hour = 0; int day = 0; for (int i = items.size() - 1; i >= 0 ; i--) { GraphObject object = items.get(i); try { Object createdTime = object.getProperty("created_time"); if (createdTime != null) { Date created = sdf.parse(createdTime.toString()); long postTime = created.getTime(); if (now - postTime < 60 * 60 * 1000) hour += 1; if (now - postTime < (60 * 60 * 1000 * 24)) day += 1; if (postTime > mostRecent) { Object o = object.getProperty("message"); if (o != null) { Bundle eventBundle = new Bundle(); eventBundle.putString("PROBE", FacebookEventsProbe.PROBE_NAME); eventBundle.putLong("TIMESTAMP", postTime / 1000); eventBundle.putString("TYPE", object.getProperty("type").toString()); String message = o.toString(); try { if (doHash) message = em.encryptString(context, message); eventBundle.putString("MESSAGE", message); } catch (IllegalBlockSizeException | UnsupportedEncodingException | BadPaddingException e) { LogManager.getInstance(context).logException(e); } eventBundle.putBoolean("IS_OBFUSCATED", doHash); try { // Sleep so initial load is delivered in order... if (i == 0) Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } me.transmitData(context, eventBundle); if (postTime > newRecent) newRecent = postTime; } } } } catch (ParseException | NullPointerException e) { LogManager.getInstance(context).logException(e); } } Editor e = prefs.edit(); e.putLong("config_probe_facebook_recent", newRecent); e.commit(); bundle.putInt(FacebookProbe.HOUR_COUNT, hour); bundle.putInt(FacebookProbe.DAY_COUNT, day); me.transmitData(context, bundle); } catch (NullPointerException e) { LogManager.getInstance(context).logException(e); } } }); } catch (NullPointerException e) { LogManager.getInstance(context).logException(e); } } }); } } } return true; } else { Editor e = prefs.edit(); e.remove(FacebookApi.TOKEN); e.commit(); } } final SanityManager sanity = SanityManager.getInstance(context); final String title = context.getString(R.string.title_facebook_check); sanity.clearAlert(title); return false; } @Override public String summarizeValue(Context context, Bundle bundle) { double hour = bundle.getDouble(FacebookProbe.HOUR_COUNT, 0); double day = bundle.getDouble(FacebookProbe.DAY_COUNT, 0); if (hour != 1 && day != 1) return String.format(context.getResources().getString(R.string.facebook_count_desc), (int) hour, (int) day); else if (hour == 1 && day != 1) return String.format(context.getResources().getString(R.string.facebook_count_desc_single_hour), (int) day); else if (day == 1 && hour != 1) return String.format(context.getResources().getString(R.string.facebook_count_desc_single_day), (int) hour); return context.getResources().getString(R.string.facebook_count_desc_single_both); } @Override public Map<String, Object> configuration(Context context) { Map<String, Object> map = super.configuration(context); SharedPreferences prefs = Probe.getPreferences(context); long freq = Long.parseLong(prefs.getString(FacebookProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY)); map.put(Probe.PROBE_FREQUENCY, freq); boolean hash = prefs.getBoolean(FacebookProbe.ENCRYPT_DATA, FacebookProbe.DEFAULT_ENCRYPT); map.put(Probe.ENCRYPT_DATA, hash); return map; } @Override public void updateFromMap(Context context, Map<String, Object> params) { super.updateFromMap(context, params); if (params.containsKey(Probe.PROBE_FREQUENCY)) { Object frequency = params.get(Probe.PROBE_FREQUENCY); if (frequency instanceof Double) { frequency = ((Double) frequency).longValue(); } if (frequency instanceof Long) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putString(FacebookProbe.FREQUENCY, frequency.toString()); e.commit(); } } if (params.containsKey(Probe.ENCRYPT_DATA)) { Object encrypt = params.get(Probe.ENCRYPT_DATA); if (encrypt instanceof Boolean) { Boolean encryptBoolean = (Boolean) encrypt; SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FacebookProbe.ENCRYPT_DATA, encryptBoolean); e.commit(); } } } @Override public String summary(Context context) { return context.getString(R.string.summary_facebook_probe_desc); } @Override public JSONObject fetchSettings(Context context) { JSONObject settings = super.fetchSettings(context); try { JSONArray values = new JSONArray(); values.put(true); values.put(false); JSONObject encrypt = new JSONObject(); encrypt.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN); encrypt.put(Probe.PROBE_VALUES, values); settings.put(Probe.ENCRYPT_DATA, encrypt); JSONObject frequency = new JSONObject(); frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG); values = new JSONArray(); String[] options = context.getResources().getStringArray(R.array.probe_low_frequency_values); for (String option : options) { values.put(Long.parseLong(option)); } frequency.put(Probe.PROBE_VALUES, values); settings.put(Probe.PROBE_FREQUENCY, frequency); } catch (JSONException e) { LogManager.getInstance(context).logException(e); } return settings; } @Override @SuppressWarnings("deprecation") public PreferenceScreen preferenceScreen(final Context context, PreferenceManager manager) { final PreferenceScreen screen = manager.createPreferenceScreen(context); screen.setTitle(this.title(context)); screen.setSummary(R.string.summary_facebook_probe_desc); final SharedPreferences prefs = Probe.getPreferences(context); CheckBoxPreference enabled = new CheckBoxPreference(context); enabled.setTitle(R.string.title_enable_probe); enabled.setKey(FacebookProbe.ENABLED); enabled.setDefaultValue(FacebookProbe.DEFAULT_ENABLED); screen.addPreference(enabled); FlexibleListPreference duration = new FlexibleListPreference(context); duration.setKey(FacebookProbe.FREQUENCY); duration.setEntryValues(R.array.probe_low_frequency_values); duration.setEntries(R.array.probe_low_frequency_labels); duration.setTitle(R.string.probe_frequency_label); duration.setDefaultValue(Probe.DEFAULT_FREQUENCY); screen.addPreference(duration); CheckBoxPreference encrypt = new CheckBoxPreference(context); encrypt.setKey(FacebookProbe.ENCRYPT_DATA); encrypt.setDefaultValue(FacebookProbe.DEFAULT_ENCRYPT); encrypt.setTitle(R.string.config_probe_facebook_encrypt_title); encrypt.setSummary(R.string.config_probe_facebook_encrypt_summary); screen.addPreference(encrypt); final Preference authPreference = new Preference(context); authPreference.setTitle(R.string.title_authenticate_facebook_probe); authPreference.setSummary(R.string.summary_authenticate_facebook_probe); final Preference logoutPreference = new Preference(context); logoutPreference.setTitle(R.string.title_logout_facebook_probe); logoutPreference.setSummary(R.string.summary_logout_facebook_probe); authPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { Intent intent = new Intent(context, FacebookLoginActivity.class); intent.putExtra(FacebookLoginActivity.APP_ID, context.getString(R.string.facebook_app_id)); context.startActivity(intent); screen.addPreference(logoutPreference); screen.removePreference(authPreference); return true; } }); logoutPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { Editor e = prefs.edit(); e.remove(FacebookApi.TOKEN); e.commit(); screen.addPreference(authPreference); screen.removePreference(logoutPreference); if (context instanceof Activity) { Activity activity = (Activity) context; activity.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, context.getString(R.string.toast_facebook_logout), Toast.LENGTH_LONG).show(); } }); } return true; } }); final String token = prefs.getString(FacebookApi.TOKEN, null); if (token == null) screen.addPreference(authPreference); else screen.addPreference(logoutPreference); return screen; } }